package com.zyt.wiki.service;

import com.zyt.wiki.domain.Content;
import com.zyt.wiki.domain.Doc;
import com.zyt.wiki.domain.DocExample;
import com.zyt.wiki.exception.BusinessException;
import com.zyt.wiki.exception.BusinessExceptionCode;
import com.zyt.wiki.mapper.ContentMapper;
import com.zyt.wiki.mapper.DocMapper;
import com.zyt.wiki.mapper.DocMapperCust;
import com.zyt.wiki.req.DocSaveReq;
import com.zyt.wiki.resp.DocQueryResp;
import com.zyt.wiki.util.CopyUtil;
import com.zyt.wiki.util.RedisUtil;
import com.zyt.wiki.util.RequestContext;
import com.zyt.wiki.util.SnowFlake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.List;

@Service
public class DocService {

    public static final Logger LOG = LoggerFactory.getLogger(DocService.class) ;

    @Autowired  // 【Spring自带】，通过 byType 的方式去注入的， 使用该注解，要求接口只能有一个实现类
    private DocMapper docMapper ;

    @Resource
    private DocMapperCust docMapperCust ;

    @Resource   // 【JDK自带】，通过 byName 和 byType的方式注入， 默认先按 byName的方式进行匹配，如果匹配不到，再按 byType的方式进行匹配
    private SnowFlake snowFlake ;

    @Resource
    private ContentMapper contentMapper ;

    @Resource
    private RedisUtil redisUtil ;

    @Resource
    private WebSocketService webSocketService ;

//    @Resource
//    private RocketMQTemplate rocketMQTemplate ;

    /** 查询文档列表，不分页 */
    public List<DocQueryResp> all(Long ebookId){
        DocExample docExample = new DocExample() ;
        docExample.createCriteria().andEbookIdEqualTo(ebookId) ;
        docExample.setOrderByClause("sort asc");   //按sort字段升序
        List<Doc> docList = this.docMapper.selectByExample(docExample);
        List<DocQueryResp> respList = CopyUtil.copyList(docList, DocQueryResp.class);
        return respList ;
    }

    /** 保存 */
    @Transactional // 事务控制（注意：与@Async注解类似，该注解也要生成代理类，要在其他类中调用该方法，若在当前类中调用，则事务无效）
    public void save(DocSaveReq req){
        Doc doc = CopyUtil.copy(req, Doc.class) ;  // Mapper操作的是Doc对象，所以要转换一下
        Content content = CopyUtil.copy(req, Content.class);
        if(ObjectUtils.isEmpty(doc.getId())){
            // 新增 （前端没有上送主键Id）
            doc.setId(this.snowFlake.nextId());
            doc.setViewCount(0) ; // 新增时，上送的阅读数和点赞数为空，入库后为null，而null值+1自增无效，所以这里要初始为0
            doc.setVoteCount(0) ;
            this.docMapper.insert(doc) ;

            content.setId(doc.getId());
            this.contentMapper.insert(content) ;
        }else {
            // 修改
            this.docMapper.updateByPrimaryKey(doc) ;
            // 更新DB时，带~WithBLOBs是会更新大字段的，不带~WithBLOBs是不会更新大字段的（可跟踪到mapper.xml中取查询sql核实）
            int count = this.contentMapper.updateByPrimaryKeyWithBLOBs(content) ;
            if(count == 0){ // 若文档内容原来不存在，则更新记录会为0，此时直接插入记录
                this.contentMapper.insert(content) ;
            }
        }
    }

    /** 删除 */
    public void delete(Long id){
        this.docMapper.deleteByPrimaryKey(id) ;
    }

    /** 批量删除， idsStr格式：id1,id2,id3 */
    public void delete(List<String> ids){
        DocExample docExample = new DocExample() ;
        DocExample.Criteria criteria = docExample.createCriteria();
        criteria.andIdIn(ids) ;
        this.docMapper.deleteByExample(docExample) ;
    }

    /** 查询文档内容 */
    public String findContent(Long id){
        String contentStr = null ;
        Content content = this.contentMapper.selectByPrimaryKey(id) ;  //通过查看sql，该方法是能查出所有字段（包含大字段）的，可以放心使用
        this.docMapperCust.increaseViewCount(id) ; // 文档阅读数 + 1 （注：由于当前项目的文档管理编辑时，也通过当前方法查询内容，所以也会累加，实际生产场景要拆分开）
        if(!ObjectUtils.isEmpty(content)){
            contentStr = content.getContent() ;
        }
        return contentStr ;
    }

    /** 点赞文档 */
    public void vote(Long id){
        // 远程IP+doc.id作为key，24小时内不能重复 (也可以把存放Redis改为存到DB中)
        String ip = RequestContext.getRemoteAddr() ;
        boolean canVote = this.redisUtil.validateRepeat1("DOC_VOTE_" + id + "_" + ip, 3600 * 24) ;
        if(canVote){
            this.docMapperCust.increaseVoteCount(id) ;  // 文档点赞数 + 1
        }else {
            throw new BusinessException(BusinessExceptionCode.VOTE_REPEAT) ;
        }
        // 推送消息（通过WebSocket推送给前端）
        // 【异步化，避免通知功能出错影响到点赞功能】 注意：@Async异步化方法要定义在其他类中（该注解要生成代理类），若在当前类中调用，则异步化无效
        Doc docDb = this.docMapper.selectByPrimaryKey(id);
        String logId = MDC.get("LOG_ID");
        this.webSocketService.sendInfoAsync("【" + docDb.getName() + "】被点赞！", logId);
        // MQ生产者，向主题VOTE_TOPIC发送一条消息
//        this.rocketMQTemplate.convertAndSend("VOTE_TOPIC", "【" + docDb.getName() + "】被点赞！");
    }

    /** 更新ebook表信息(文档数\阅读数\点赞数) */
    public void updateEbookInfo(){
        this.docMapperCust.updateEbookInfo() ;
    }

}
